import Foundation

import MarketKit
import RxCocoa
import RxRelay
import RxSwift
import UIKit

class WalletConnectMainViewModel {
    private let scheduler = SerialDispatchQueueScheduler(qos: .userInitiated, internalSerialQueueName: "\(AppConfig.label).wallet_connect_main")

    private let service: WalletConnectMainService
    private let disposeBag = DisposeBag()

    private let showErrorRelay = PublishRelay<String>()
    private let showSuccessRelay = PublishRelay<Void>()
    private let showDisconnectRelay = PublishRelay<Void>()
    private let showTimeOutAttentionRelay = PublishRelay<Void>()
    private let connectingRelay = BehaviorRelay<Bool>(value: false)
    private let cancelVisibleRelay = BehaviorRelay<Bool>(value: false)
    private let connectButtonRelay = BehaviorRelay<ButtonState>(value: .hidden)
    private let reconnectButtonRelay = BehaviorRelay<ButtonState>(value: .hidden)
    private let disconnectButtonRelay = BehaviorRelay<ButtonState>(value: .hidden)
    private let closeVisibleRelay = BehaviorRelay<Bool>(value: false)
    private let headerTitleStateRelay = BehaviorRelay<TitleState>(value: .connect)
    private let viewItemRelay = BehaviorRelay<ViewItem?>(value: nil)
    private let finishRelay = PublishRelay<Void>()

    private let whitelistStateRelay = BehaviorRelay<WalletConnectMainModule.WhitelistState>(value: .loading)

    init(service: WalletConnectMainService) {
        self.service = service

        subscribe(scheduler, disposeBag, service.errorObservable) { [weak self] in
            self?.showErrorRelay.accept($0.smartDescription)
        }
        subscribe(scheduler, disposeBag, service.stateObservable) { [weak self] state in
            self?.sync(state: state)
        }
        subscribe(scheduler, disposeBag, service.connectionStateObservable) { [weak self] connectionState in
            self?.sync(connectionState: connectionState)
        }
        subscribe(scheduler, disposeBag, service.allowedBlockchainsObservable) { [weak self] allowedBlockchains in
            self?.sync(allowedBlockchains: allowedBlockchains)
        }
        subscribe(scheduler, disposeBag, service.proposalTimeOutAttentionObservable) { [weak self] in self?.showTimeOutAttentionRelay.accept(()) }

        subscribe(scheduler, disposeBag, service.whitelistStateObservable) { [weak self] state in self?.syncWhitelist(state: state) }

        subscribe(MainScheduler.instance, disposeBag, service.connectedObservable) { [weak self] in
            HudHelper.instance.show(banner: .connectedWalletConnect)
            self?.finishRelay.accept(())
        }

        sync()
    }

    private func dAppMetaViewItem(appMetaItem: WalletConnectMainModule.AppMetaItem) -> DAppMetaViewItem {
        DAppMetaViewItem(
            name: appMetaItem.name,
            url: appMetaItem.url,
            icon: appMetaItem.icons.last
        )
    }

    private func sync(state: WalletConnectMainModule.State? = nil, connectionState: WalletConnectMainModule.ConnectionState? = nil, allowedBlockchains: [WalletConnectMainModule.BlockchainProposal]? = nil) {
        let state = state ?? service.state
        let connectionState = connectionState ?? service.connectionState
        let allowedBlockchains = allowedBlockchains ?? service.allowedBlockchains

        if case let .killed(reason) = state {
//            showSuccessRelay.accept(())
            switch reason {
            case .rejectProposal: ()
            case .killSession, .rejectSession: showDisconnectRelay.accept(())
            }

            finishRelay.accept(())
            return
        }

        syncWhitelist(state: service.whitelistState)

        connectingRelay.accept(service.state == .idle)
        cancelVisibleRelay.accept(state != .ready)
        connectButtonRelay.accept(state == .waitingForApproveSession ? (connectionState == .connected ? .enabled : .hidden) : .hidden)
        disconnectButtonRelay.accept(state == .ready ? (connectionState == .connected ? .enabled : .hidden) : .hidden)
        headerTitleStateRelay.accept(state == .ready ? (connectionState == .connected ? .connected : .connect) : .connect)

        let stateForReconnectButton = state == .waitingForApproveSession || state == .ready
        reconnectButtonRelay.accept(stateForReconnectButton ? (connectionState == .disconnected ? .enabled : .hidden) : .hidden)
        closeVisibleRelay.accept(state == .ready)

        let blockchains = allowedBlockchains
            .map { blockchain in
                BlockchainViewItem(
                    chainId: blockchain.item.chainId,
                    blockchain: blockchain.item.blockchain
                )
            }

        let viewItem = ViewItem(
            dAppMeta: service.appMetaItem.map { dAppMetaViewItem(appMetaItem: $0) },
            status: status(connectionState: connectionState),
            activeAccountName: service.activeAccountName,
            blockchains: blockchains,
            hint: service.hint
        )

        viewItemRelay.accept(viewItem)
    }

    private func syncWhitelist(state: WalletConnectMainModule.WhitelistState) {
        guard service.premiumEnabled else {
            whitelistStateRelay.accept(.deactivated)
            return
        }

        whitelistStateRelay.accept(state)
    }

    private func status(connectionState: WalletConnectMainModule.ConnectionState) -> Status? {
        guard service.appMetaItem != nil else {
            return nil
        }

        switch connectionState {
        case .connecting:
            return .connecting
        case .connected:
            return .online
        case .disconnected:
            return .offline
        }
    }
}

extension WalletConnectMainViewModel {
    var showErrorSignal: Signal<String> {
        showErrorRelay.asSignal()
    }

    var showSuccessSignal: Signal<Void> {
        showSuccessRelay.asSignal()
    }

    var showDisconnectSignal: Signal<Void> {
        showDisconnectRelay.asSignal()
    }

    var showTimeOutAttentionSignal: Signal<Void> {
        showTimeOutAttentionRelay.asSignal()
    }

    var connectingDriver: Driver<Bool> {
        connectingRelay.asDriver()
    }

    var cancelVisibleDriver: Driver<Bool> {
        cancelVisibleRelay.asDriver()
    }

    var connectButtonDriver: Driver<ButtonState> {
        connectButtonRelay.asDriver()
    }

    var reconnectButtonDriver: Driver<ButtonState> {
        reconnectButtonRelay.asDriver()
    }

    var disconnectButtonDriver: Driver<ButtonState> {
        disconnectButtonRelay.asDriver()
    }

    var headerTitleStateDriver: Driver<TitleState> {
        headerTitleStateRelay.asDriver()
    }

    var whitelistStateDriver: Driver<WalletConnectMainModule.WhitelistState> {
        whitelistStateRelay.asDriver()
    }

    var closeVisibleDriver: Driver<Bool> {
        closeVisibleRelay.asDriver()
    }

    var viewItemDriver: Driver<ViewItem?> {
        viewItemRelay.asDriver()
    }

    var finishSignal: Signal<Void> {
        finishRelay.asSignal()
    }

    var premiumEnabled: Bool {
        service.premiumEnabled
    }

    func cancel() {
        if service.connectionState == .connected, service.state == .waitingForApproveSession {
            service.rejectSession()
        } else {
            finishRelay.accept(())
        }
    }

    func reconnect() {
        service.reconnect()
    }

    func connect() {
        service.approveSession()
    }

    func reject() {
        service.rejectSession()
    }

    func disconnect() {
        service.killSession()
    }

    func close() {
        finishRelay.accept(())
    }
}

extension WalletConnectMainViewModel {
    enum TitleState {
        case connect
        case connected

        func title(name: String) -> String {
            switch self {
            case .connect: return "wallet_connect.main.connect".localized(name)
            case .connected: return "wallet_connect.main.connected".localized(name)
            }
        }
    }

    struct ViewItem {
        let dAppMeta: DAppMetaViewItem?
        let status: Status?
        let activeAccountName: String?

        // v2
        let blockchains: [BlockchainViewItem]?

        let hint: String?
    }

    struct DAppMetaViewItem {
        let name: String
        let url: String
        let icon: String?
    }

    struct BlockchainViewItem {
        let chainId: String
        let blockchain: Blockchain
    }

    struct BlockchainSelectorViewItem {
        let chainId: Int
        let title: String
        let imageUrl: String
        let selected: Bool
    }

    enum Status {
        case connecting
        case online
        case offline

        var color: UIColor {
            switch self {
            case .connecting: return .themeLeah
            case .offline: return .themeLucian
            case .online: return .themeRemus
            }
        }

        var title: String {
            switch self {
            case .connecting: return "connecting".localized
            case .offline: return "offline".localized
            case .online: return "online".localized
            }
        }
    }
}

extension WalletConnectMainModule.SessionError: LocalizedError {
    public var errorDescription: String? {
        switch self {
        case .noAnySupportedChainId: return "wallet_connect.main.no_any_supported_chains".localized
        case .unsupportedChainId: return "wallet_connect.main.unsupported_chains".localized
        default: return nil
        }
    }
}

extension ProposalValidator.ValidateError: LocalizedError {
    public var errorDescription: String? {
        switch self {
        case let .unsupportedReference(namespace: namespace, reference: reference): return "Unsupported \(namespace) blockchain with chainId: \(reference)".localized
        case let .unsupportedMethod(method: method): return "Unsupported method: \(method)".localized
        case let .unsupportedEvent(event: event): return "Unsupported event: \(event)".localized
        }
    }
}
